`include "define.svh"

module id_wrap(
    input wire                              clk,
    input wire                              rstn,
    
    input wire [5 : 0]                      hardware_int,
    input wire [1 : 0]                      software_int,
    input wire [7 : 0]                      cp0_status_im,
    input wire                              cp0_status_ie,
    input wire                              cp0_status_exl,
    
    input wire                              ctrl_stall_i,
    input wire                              pipeline_flush_i,
    input wire                              addr_ok,
    input wire                              data_ok,
    
    input wire [`PC_WIDTH - 1 : 0]          pc_i, 
    input wire [`INST_WIDTH - 1 : 0]        inst_i,
    
    output reg [`REG_ADDR_WIDTH - 1 : 0]    raddr1_o,           // id_cell output
    output reg [`REG_ADDR_WIDTH - 1 : 0]    raddr2_o,           // id_cell output
    
    input wire [`REG_WIDTH - 1 : 0]         reg_rdata1_i,
    input wire [`REG_WIDTH - 1 : 0]         reg_rdata2_i,
    
    input wire [`REG_WIDTH - 1 : 0]         rhidata_i,
    input wire [`REG_WIDTH - 1 : 0]         rlodata_i,
    
    input wire                              ex_whien_i,
    input wire [`REG_WIDTH - 1 : 0]         ex_whidata_i,
    input wire                              ex_wloen_i,
    input wire [`REG_WIDTH - 1 : 0]         ex_wlodata_i,
    
    input wire                              ex_wenable_i,
    input wire [`REG_ADDR_WIDTH - 1 : 0]    ex_waddr_i,
    input wire [`REG_WIDTH - 1 : 0]         ex_wdata_i,
    input wire [3 : 0]                      ex_data2reg_en_i,
    
    input wire                              ex_cp0_wen_i, 
    input wire [`REG_ADDR_WIDTH - 1 : 0]    ex_cp0_waddr_i, 
    input wire [`REG_WIDTH - 1 : 0]         ex_cp0_wdata_i,
    
    input wire                              mem_wenable_i,
    input wire [`REG_ADDR_WIDTH - 1 : 0]    mem_waddr_i,
    input wire [`REG_WIDTH - 1 : 0]         mem_wdata_i,
    
    output reg [`REG_WIDTH - 1 : 0]         opprand1_o,         // id_cell output
    output reg [`REG_WIDTH - 1 : 0]         opprand2_o,         // id_cell output
    output reg [`ALUOP_WIDTH - 1 : 0]       aluop_o,            // id_cell output
    output reg [`REG_ADDR_WIDTH - 1 : 0]    wregaddr_o,         // id_cell output
    output reg                              wregenable_o,       // id_cell output
    
    output reg                              whien_o,            // id_cell output
    output reg                              wloen_o,            // id_cell output
    output reg [`REG_WIDTH - 1 : 0]         whilodata_o,        // id_cell output
    
    output reg                              branch_flag_o,      // id_cell output
    output reg [`PC_WIDTH - 1 : 0]          branch_addr_o,      // id_cell output
    
    output reg [3 : 0]                      rdataen_o,          // id_cell output
    output reg                              rdatasigned_o,      // id_cell output
    output reg [3 : 0]                      wdataen_o,          // id_cell output
    output reg [`REG_WIDTH - 1 : 0]         wdata_o,            // id_cell output
    
    output reg [`REG_WIDTH - 1 : 0]         debug_pc_o,
    output reg                              pc_in_delay_slot,
    
    output reg [`EXCCODE_WIDTH - 1 : 0]     exc_code,
    output reg [`PC_WIDTH - 1 : 0]          error_pc,
    
    output reg                              load_use_stall_request,
    output reg                              inst_stall_request,
    
    output reg [`REG_ADDR_WIDTH - 1 : 0]    cp0_raddr_o,        // id_cell output
    input wire [`REG_WIDTH - 1 : 0]         cp0_rdata_i,
    output reg                              mtc_wen,            // id_cell output
    output reg [`REG_ADDR_WIDTH - 1 : 0]    mtc_waddr,          // id_cell output
    output reg [`REG_WIDTH - 1 : 0]         mtc_wdata           // id_cell output
);    
    
    
        
    
    /* ***************** INST and PC ********************** */
    reg [`REG_WIDTH - 1 : 0]    inst_r;       // current inst (inst_i or stall_inst from ctrl cache)
    reg [`REG_WIDTH - 1 : 0]    inst;
    reg [`PC_WIDTH - 1 : 0]     pc_pcreg;   // clock control, pc of current inst at id-stage
    
    
    reg pipeline_flush;
    always_ff @(posedge clk)
        if (rstn == `reset)                 pipeline_flush <= `false;
        else if (pipeline_flush_i)          pipeline_flush <= `true;
        else if (~ctrl_stall_i)             pipeline_flush <= `false;
    
    reg [`PC_WIDTH - 1 : 0]     stall_inst;
    reg                         stall_inst_en;
    always_ff @(posedge clk)
        if (data_ok & ctrl_stall_i & ~stall_inst_en) begin
            stall_inst <= inst_i;
            stall_inst_en <= `true;
        end else if (~ctrl_stall_i) begin
            stall_inst <= `ZERO_REG;
            stall_inst_en <= `false;
        end
    
    always_ff @(posedge clk) begin
        if (rstn == `reset)                         inst_r <= `ZERO_REG;
        else if (pipeline_flush)                    inst_r <= `ZERO_REG;
        else if (ctrl_stall_i)                      inst_r <= inst_r;
        else if (stall_inst_en)                     inst_r <= stall_inst;
        else                                        inst_r <= inst_i;
    end
    
    always_ff @(posedge clk) begin
        if (rstn == `reset)                         pc_pcreg <= 32'hBFC00000;
        else if (pipeline_flush)                    pc_pcreg <= 32'hBFC00000;
        else if (ctrl_stall_i)                      pc_pcreg <= pc_pcreg;
        else                                        pc_pcreg <= pc_i;
    end 
    
    always_comb     inst = pc_pcreg == `NOP_PC ? `ZERO_REG: inst_r;
    
    
    /* ************ Load-use hazard ************* */
    always_comb 
        load_use_stall_request = (~(pipeline_flush)) & 
            (ex_data2reg_en_i && (raddr1_o == ex_waddr_i || raddr2_o == ex_waddr_i));
    
    always_comb inst_stall_request = ~data_ok;
    
    /* **************** Registers read with forwarding ******************* */
    /* Register read with forwarding */
    reg [`REG_WIDTH - 1 : 0]          reg_rdata1;
    reg [`REG_WIDTH - 1 : 0]          reg_rdata2;
    wire is_reg1_ex_forwarding = ex_wenable_i && (ex_waddr_i == raddr1_o);
    wire is_reg1_mem_forwarding = mem_wenable_i && (mem_waddr_i == raddr1_o);
    wire is_reg2_ex_forwarding = ex_wenable_i && (ex_waddr_i == raddr2_o);
    wire is_reg2_mem_forwarding = mem_wenable_i && (mem_waddr_i == raddr2_o);
    always_comb
        case ({is_reg1_ex_forwarding, is_reg1_mem_forwarding})
            2'b00:      reg_rdata1 = reg_rdata1_i;
            2'b01:      reg_rdata1 = mem_wdata_i;
            2'b10:      reg_rdata1 = ex_wdata_i;
            2'b11:      reg_rdata1 = ex_wdata_i;
        endcase
    always_comb
        case ({is_reg2_ex_forwarding, is_reg2_mem_forwarding})
            2'b00:      reg_rdata2 = reg_rdata2_i;
            2'b01:      reg_rdata2 = mem_wdata_i;
            2'b10:      reg_rdata2 = ex_wdata_i;
            2'b11:      reg_rdata2 = ex_wdata_i;
        endcase
    /* HILO register read with forwarding */
    reg [`REG_WIDTH - 1 : 0]    rhidata;
    reg [`REG_WIDTH - 1 : 0]    rlodata;
    always_comb begin
        if (ex_whien_i)         rhidata = ex_whidata_i;
        else                    rhidata = rhidata_i;
        if (ex_wloen_i)         rlodata = ex_wlodata_i;
        else                    rlodata = rlodata_i;
    end
    /* CP0 read with forwarding */
    reg [`REG_WIDTH - 1 : 0]    cp0_rdata;
    always_comb 
        cp0_rdata = (ex_cp0_wen_i && ex_cp0_waddr_i == cp0_raddr_o) ? ex_cp0_wdata_i : cp0_rdata_i;
    
    
    // All exception signal.
    reg                Sys_exc;
    reg                Bp_exc;
    reg                RI_exc;
    reg                ERET_exc;
    reg                pc_AdEL;
    reg                has_int;
    reg                branch_instr;
    // if current instr in delay slot. 
    // It's different when processing exception in/not in delay slot.
    
    id id_cell(
        .pc_i           (pc_pcreg       ),
        .inst_i         (inst           ),
        .is_branch_inst (branch_instr   ),
        .reg_rdata1_i   (reg_rdata1     ),
        .reg_rdata2_i   (reg_rdata2     ),
        .rhidata_i      (rhidata        ),
        .rlodata_i      (rlodata        ),
        .raddr1_o       (raddr1_o       ),
        .raddr2_o       (raddr2_o       ),
        .opprand1_o     (opprand1_o     ),
        .opprand2_o     (opprand2_o     ),
        .aluop_o        (aluop_o        ),
        .wregaddr_o     (wregaddr_o     ),
        .wregenable_o   (wregenable_o   ),
        .whien_o        (whien_o        ),
        .wloen_o        (wloen_o        ),
        .whilodata_o    (whilodata_o    ),
        .branch_flag_o  (branch_flag_o  ),
        .branch_addr_o  (branch_addr_o  ),
        .rdataen_o      (rdataen_o      ),
        .rdatasigned_o  (rdatasigned_o  ),
        .wdataen_o      (wdataen_o      ),
        .wdata_o        (wdata_o        ),
        .cp0_raddr_o    (cp0_raddr_o    ),
        .cp0_rdata_i    (cp0_rdata      ),
        .mtc_wen        (mtc_wen        ),
        .mtc_waddr      (mtc_waddr      ),
        .mtc_wdata      (mtc_wdata      ),
        .Sys_exc        (Sys_exc        ),
        .Bp_exc         (Bp_exc         ),
        .RI_exc         (RI_exc         ),
        .ERET_exc       (ERET_exc       )
    );
    
    
    /* ************************ OUTPUT ********************** */
    // pc & pc_in_delay_slot signal.
    // These outputs are passed to next stage(ex-stage).
    always_comb        debug_pc_o = pc_pcreg;
    reg     last_inst_branch;
    // Reg: if last instr is a branch instr.
    // if true, then current instr is in delay slot.
    always_ff @(posedge clk) begin
        if (rstn == `reset)     last_inst_branch <= `false;
        else if (ctrl_stall_i)  last_inst_branch <= last_inst_branch;
        else                    last_inst_branch <= branch_instr;
    end 
    always_comb pc_in_delay_slot = last_inst_branch;
    
    /* ************* EXCEPTION ************** */
    // pc_AdEL, SYSCALL, BREAKPOINT, RI, ERET, ExternalInterupt will be detect here
    // and signals will be send to EX-stage.
    always_comb pc_AdEL = pc_pcreg[0] | pc_pcreg[1];
    wire [7 : 0] external_int = {hardware_int, software_int};
    always_comb
        has_int = cp0_status_ie && (~cp0_status_exl) && ((external_int & cp0_status_im) != 8'h00);
    // error_pc is used for pc_AdEL, when this exception occured, 
    // The wrong value of pc will stored into BadVAddr register in CP0.
    always_ff @(posedge clk) begin
        if (rstn == `reset)                       error_pc <= 32'hBFC00000;
        else if (ctrl_stall_i)                    error_pc <= error_pc;
        else                                      error_pc <= pc_i;
    end
    always_comb 
        case({has_int, pc_AdEL, Sys_exc, Bp_exc, RI_exc, ERET_exc})
            6'b100_000:     exc_code = `Exc_Int;
            6'b010_000:     exc_code = `Exc_PCAdEL;
            6'b001_000:     exc_code = `Exc_Sys;
            6'b000_100:     exc_code = `Exc_Bp;
            6'b000_010:     exc_code = `Exc_RI;
            6'b000_001:     exc_code = `Exc_ERET;
            default:        exc_code = `Exc_None;
        endcase
        
endmodule
